/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file game_object.hpp
 * @author TooOld2Rock'nRoll
 * @date 2023/04/10
 * @brief Implements the GameObject Class for the ArcadeFighter library.
 *
 * @todo Add a name member to the class, use the pair Kind/Name on all debug message so it is easier to trace what is going on without turning on VERBOSE.
 *       Make it exist only when debug is active.
 */
#ifndef _GAME_OBJECT_HPP_
#define _GAME_OBJECT_HPP_

/*---- Includes ----*/
#include "resource_manager.hpp"
#include "graphics/shader.hpp"
#include "graphics/sprite2D.hpp"


/*---- Class Declaration ----*/
/**
 * @brief Class that all other game objects derive from.
 *
 * This is a simplification from the Component design pattern from Roberts Nystrom's book
 *    "<i>Game Programming Patterns</i>". It will be used by the Level class as a way to store and
 *    cycle through user defined objects that generally intended to be rendered to the screen.<br>
 * A GameObject is expected to have just one texture, but it may have as many sprites as it needs. Your image
 *    files may be a sprite sheet containing many smaller images and animation frames or states of a
 *    background, etc, so by just changing the UV of a specific Sprite2D you can reuse the same Texture
 *    to show different images with no need to change its parameters over and over.<br>
 * Regarding resource allocation, we are working on the assumption the Level will have its own resource id and pass it
 *    down to the GameObjects it controls.<br>
 * The shader for rendering its sprites can be specific to the GameObject and created on its constructor or passed down
 *    from the Level like the resource id.
 *
 * @see https://gameprogrammingpatterns.com/component.html
 */
class GameObject
{
    public:
        /** @brief Set of know GameObject specializations.  */
        enum kind
        {
            GENERIC = 0,///< GameObject! A generic object with no specif or relevant role in the game.
            SCENERY,    ///< Things like background, generally <b>not</b> interactive.
            PROP,       ///< Small items around the level (or big, not judging...).
            CHARACTER,  ///< The controllable characters in the game.
            EXTENDED,   ///< User extended GameObject classes that are none of the above.
            _kind_max   ///< Keep track of how many valid game objects there are.
        };//end kind


    private:
        //how many sprites is this class coordinating
        unsigned ui_sprite_count = 0;
        //the list of sprites
        Sprite2D *ap_sprite_list = nullptr;


    protected:
        ///Some actions can only be performed on some GameObject specializations, so we keep track of what is what.
        GameObject::kind obj_kind = GameObject::GENERIC; //should be const, but can't find a way to change value on derived classes.....

        ResourceManager *p_resource_manager = nullptr; ///<Local reference to the ResourceManager singleton.
        DomainID_t _res_domain_id = ResourceManager::global_domain_id; ///<To which resource domain does the GameObject belongs.

        ///Pointer to the Shader to be used in all rendering operations for this GameObject instance.
        Shader *obj_shader = nullptr;

        /** @brief GameObject world position property.
         *  @remarks It is useful to move groups of sprites in relation to this position, by entangling it for instance.
         */
        Position v3_world_position;


    public:
        GameObject (DomainID_t domain_id, Shader *sh = nullptr, unsigned sprite_count = 1);
        virtual ~GameObject ();

        /** @brief Dedicated method for loading the GameObject resources (like Textures).
         * If the GameObject is simple, the constructor can handle loading resources without blocking the UI for too long.<br>
         * But! Sometimes a dedicated method may be necessary to compartimentalize the process and/or using a working thread. */
        virtual void load () {  }
        /** @brief Dedicated method for setting up the GameObject resources.
         * If the GameObject is simple, the constructor or the loading method can handle setting up everything to a usable state.<br>
         * But! Sometimes a dedicated method may be necessary to compartimentalize the process and/or using a working thread
         *    since some procedures (like OpenGl shared contexts) "don't stick" when called from a thread. */
        virtual void init () {  }

        /** @brief Gets which kind of GameObject this is. */
        GameObject::kind getKind () const { return this->obj_kind; }

        void setTexture (Texture *t);

        /** @brief Gets the GameObject global position property. */
        Position & getPosition () { return this->v3_world_position; }

        /** @brief Gets the number of sprites this GameObject has. */
        unsigned getSpriteCount () const { return this->ui_sprite_count; }
        /** @brief Gets an array of all the sprites contained in this GameObject. */
        Sprite2D* getSprites () const { return this->ap_sprite_list; }
        Sprite2D& getSprite (unsigned index) const;

        /**
         * @brief Interface to State Update Callback.
         *
         * @param delta_t - time elapsed since last game loop iteration.
         */
        virtual void update (double delta_t) = 0;

        /**
         * @brief Interface to Draw Callback.
         *
         * @param delta_t - time elapsed since last game loop iteration.
         */
        virtual void draw () = 0;
};//END GameObject

#endif //_GAME_OBJECT_HPP_

